#include #include #include #include #include #include #include #include #include #include #include #include #define S0(x) (x ? x : "") enum entry { TALLY, QUOTA }; #define TYPE_SHIFT (8*7) #define STATION_SHIFT (8*6) #define VALUE_SHIFT (8*5) #define TYPE_INS(x) ((uint64_t) x << TYPE_SHIFT) #define STATION_INS(x) ((uint64_t) x << STATION_SHIFT) #define VALUE_INS(x) ((uint64_t) x << VALUE_SHIFT) #define TYPE_RET(x) ((x & TYPE_INS(0xFF)) >> TYPE_SHIFT) #define STATION_RET(x) ((x & STATION_INS(0xFF)) >> STATION_SHIFT) #define VALUE_RET(x) ((x & VALUE_INS(0xFF)) >> VALUE_SHIFT) #define TIME 0xFFFFFFFF char * response = NULL; size_t response_len = 0; size_t write_callback (char * ptr, size_t size, size_t nmemb, void * userdata __attribute__((unused))) { nmemb *= size; char * mem = realloc(response, response_len+nmemb+1); if (!mem) return 0; response = mem; strncpy(response+response_len, ptr, nmemb); response[(response_len += nmemb)] = '\0'; return nmemb; } int main (int argc, char ** argv) { cJSON * json = NULL; int r = 0; CURL * curl = NULL; unsigned char tally[256]; unsigned char quota[256]; memset(tally, 255, 256); memset(quota, 255, 256); if (fseek(stdin, -8, SEEK_END) == -1) { if (errno == EINVAL) fprintf(stderr, "nova (prazna) podatkovna zbirka!\n"); else error_at_line(1, errno, __FILE__, __LINE__, "fseek. uporaba: %s >> db < db", S0(argv[0])); } uint64_t entry; while (fread(&entry, sizeof entry, 1, stdin)) { entry = be64toh(entry); char čas[256]; time_t time = entry & TIME; strftime(čas, 256, "%c", localtime(&time)); switch (TYPE_RET(entry)) { case TALLY: if (tally[STATION_RET(entry)] == 255) { tally[STATION_RET(entry)] = VALUE_RET(entry); fprintf(stderr, "[db] %s: na postaji %" PRIu64 " je %" PRIu64 " koles\n", čas, STATION_RET(entry), VALUE_RET(entry)); } break; case QUOTA: if (quota[STATION_RET(entry)] == 255) { quota[STATION_RET(entry)] = VALUE_RET(entry); fprintf(stderr, "[db] %s: na postaji %" PRIu64 " je %" PRIu64 " postajališč\n", čas, STATION_RET(entry), VALUE_RET(entry)); } break; default: error_at_line(2, 0, __FILE__, __LINE__, "invalid entry with type %" PRIu64, TYPE_RET(entry)); } if (fseek(stdin, -16, SEEK_CUR) == -1) break; } char curlerr[CURL_ERROR_SIZE]; curl = curl_easy_init(); if (!curl) error_at_line(2, 0, __FILE__, __LINE__, "!curl"); curl_easy_setopt(curl, CURLOPT_URL, "https://api.jcdecaux.com/vls/v3/stations?apiKey=frifk0jbxfefqqniqez09tw4jvk37wyf823b5j1i&contract=ljubljana"); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curlerr); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); char buf[256]; snprintf(buf, 255, "bicikelj-stat/0.0.0 sends at most one request per 50 seconds. contact by email: %s", argc > 1 ? argv[1] : "not provided"); curl_easy_setopt(curl, CURLOPT_USERAGENT, buf); while (1) { fprintf(stderr, "."); curlerr[0] = '\0'; response_len = 0; CURLcode res = curl_easy_perform(curl); if (res != CURLE_OK) { error_at_line(0, 0, __FILE__, __LINE__, "!= CURLE_OK: %s", curlerr[0] ? curlerr : curl_easy_strerror(res)); r = 3; goto r; } json = cJSON_Parse(response); cJSON * station = NULL; cJSON_ArrayForEach(station, json) { cJSON * number = cJSON_GetObjectItem(station, "number"); cJSON * name = cJSON_GetObjectItem(station, "name"); cJSON * capacity = cJSON_GetObjectItem(cJSON_GetObjectItem(station, "totalStands"), "capacity"); cJSON * bikes = cJSON_GetObjectItem(cJSON_GetObjectItem(cJSON_GetObjectItem(station, "totalStands"), "availabilities"), "bikes"); if (!cJSON_IsNumber(number)) { error_at_line(0, 0, __FILE__, __LINE__, "number"); r = 4; goto r; } if (!cJSON_IsNumber(capacity)) { error_at_line(0, 0, __FILE__, __LINE__, "capacity"); r = 5; goto r; } if (!cJSON_IsNumber(bikes)) { error_at_line(0, 0, __FILE__, __LINE__, "bikes"); r = 6; goto r; } if (!cJSON_IsString(name)) { error_at_line(0, 0, __FILE__, __LINE__, "name"); r = 7; goto r; } if (number->valueint >= 255 || capacity->valueint >= 255 || bikes->valueint >= 255) { error_at_line(0, 0, __FILE__, __LINE__, "number || capacity || bikes >= 255: evil server!"); r = 8; goto r; } char čas[256]; time_t tajm = entry = time(NULL); strftime(čas, 256, "%c", localtime(&tajm)); if (quota[number->valueint] != capacity->valueint) { unsigned old = quota[number->valueint]; entry |= TYPE_INS(QUOTA) | STATION_INS(number->valueint) | VALUE_INS(capacity->valueint); if (old == 255) fprintf(stderr, "%s: na postaji %s (%d) je %d postajališč\n", čas, name->valuestring, number->valueint, capacity->valueint); else fprintf(stderr, "%s: na postaji %s (%d) je %d postajališč (prej %u)\n", čas, name->valuestring, number->valueint, capacity->valueint, old); quota[number->valueint] = capacity->valueint; entry = htobe64(entry); fwrite(&entry, sizeof entry, 1, stdout); } entry = time(NULL); if (tally[number->valueint] != bikes->valueint) { unsigned old = tally[number->valueint]; entry |= TYPE_INS(TALLY) | STATION_INS(number->valueint) | VALUE_INS(bikes->valueint); if (old == 255) fprintf(stderr, "%s: na postaji %s (%d) je %d koles\n", čas, name->valuestring, number->valueint, bikes->valueint); else fprintf(stderr, "%s: na postaji %s (%d) je %d koles (prej %u)\n", čas, name->valuestring, number->valueint, bikes->valueint, old); tally[number->valueint] = bikes->valueint; entry = htobe64(entry); fwrite(&entry, sizeof entry, 1, stdout); } fflush(stdout); } cJSON_Delete(json); json = NULL; sleep(50); } r: cJSON_Delete(json); json = NULL; curl_easy_cleanup(curl); curl = NULL; free(response); return r; }